﻿//======================================================================
// Copyright (c) 苏州同程旅游网络科技有限公司. All rights reserved.
// 所属项目：Common
// 创 建 人：郭瑞山
// 创建日期：5/12/2011 5:45:13 PM
// 用    途：web服务方法调用器
//====================================================================== 

namespace Common
{
    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    using System.Web.Services.Description;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.IO;
    using System.Reflection;
    using System.Xml;
    using System.Collections;
    using System.ComponentModel;
    using System.Collections.Specialized;
    using System.Xml.Serialization;
    using System.Xml.Schema;
    using System.Net;
    using System.Web.Caching;

    /// <summary>
    /// 在此描述XmlWebServiceMethodInvoker的说明
    /// </summary>
    public class XmlWebServiceMethodInvoker
    {
        private string wSDLPath;
        public string WSDLPath
        {
            get
            {
                return wSDLPath;
            }
            set
            {
                wSDLPath = value;
            }
        }

        private string typeName;
        public string TypeName
        {
            get
            {
                return typeName;
            }
            set
            {
                typeName = value;
            }
        }

        private int cacheDuration;
        public virtual int CacheDuration
        {
            get
            {
                return cacheDuration;
            }
            set
            {
                cacheDuration = value;
            }
        }

        private bool enableCaching;
        public virtual bool EnableCaching
        {
            get
            {
                return enableCaching;
            }
            set
            {
                enableCaching = value;
            }
        }


        private XmlWebServiceMethodInvokerCacheExpirationPolicy cacheExpirationPolicy;
        public virtual XmlWebServiceMethodInvokerCacheExpirationPolicy CacheExpirationPolicy
        {
            get
            {
                return cacheExpirationPolicy;
            }
            set
            {
                cacheExpirationPolicy = value;
            }
        }

        private string cacheKeyDependency;
        public virtual string CacheKeyDependency
        {
            get
            {
                return string.IsNullOrEmpty( cacheKeyDependency ) ? "XmlWebServiceMethodInvoker" : cacheKeyDependency;
            }
            set
            {
                cacheKeyDependency = value;
            }
        }

        private bool isUseWroxy = false;
        public virtual bool IsUseWroxy
        {
            get { return this.isUseWroxy; }
            set { this.isUseWroxy = value; }
        }

        private string wroxyURL = "";
        public virtual string WroxyURL
        {
            get { return this.wroxyURL; }
            set { this.wroxyURL = value; }
        }

        private string userName = "";
        private string password = "";
        public virtual string UserName
        {
            get { return this.userName; }
            set { this.userName = value; }
        }

        public virtual string Password
        {
            get { return this.password; }
            set { this.password = value; }
        }

        /// <summary>
        /// Downloads the WSDL document from the remote site
        /// </summary>
        /// <returns></returns>
        /*public virtual XmlReader DownloadWSDLDocument()
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            XmlResolver resolver = new XmlUrlResolver();
            resolver.Credentials = CredentialCache.DefaultCredentials;
            settings.XmlResolver = resolver;

            return XmlReader.Create( WSDLPath, settings );
        }*/

        public virtual Stream DownloadWSDLDocument()
        {
            WebRequest req = WebRequest.Create( WSDLPath );
            req.Method = "GET";
            req.Credentials = CredentialCache.DefaultCredentials;
            if ( IsUseWroxy )
            {
                WebProxy proxy = WebProxy.GetDefaultProxy();
                proxy.Address = new Uri( WroxyURL );
                proxy.Credentials = new NetworkCredential( UserName, Password );
                req.Proxy = proxy;
            }
            WebResponse res = req.GetResponse();
            return res.GetResponseStream();
        }

        /*public virtual string DownloadWSDLDocument()
        {
            WebClient client = new WebClient();
            return client.DownloadString(WSDLPath);
        }*/

        /// <summary>
        /// De-serializes the WSDL document into an instance of
        /// ServiceDescription object
        /// </summary>
        /// <param name="WSDLDocument"></param>
        /// <returns></returns>
        public virtual ServiceDescription DeserializeWSDLDocument( System.Xml.XmlReader WSDLDocument )
        {
            //return ServiceDescription.Serializer.Deserialize (WSDLDocument);
            ServiceDescription desc = ServiceDescription.Read( WSDLDocument );
            bool found = false;
            foreach ( Service service in desc.Services )
            {
                if ( service.Name == TypeName )
                {
                    found = true;
                    break;
                }
            }

            if ( !found )
                throw new Exception( "Invalid class name!" );

            return desc;
        }

        public virtual ServiceDescription DeserializeWSDLDocument( Stream WSDLDocument )
        {
            //return ServiceDescription.Serializer.Deserialize (WSDLDocument);
            ServiceDescription desc = ServiceDescription.Read( WSDLDocument );
            bool found = false;
            foreach ( Service service in desc.Services )
            {
                if ( service.Name == TypeName )
                {
                    found = true;
                    break;
                }
            }

            if ( !found )
                throw new Exception( "Invalid class name!" );

            return desc;
        }

        /// <summary>
        /// Generates language-agnostic code for the proxy class from the 
        /// contents of the WSDL document
        /// </summary>
        /// <param name="WSDLDocumentDescription"></param>
        /// <param name="myCodeCompileUnit"></param>
        /// <returns></returns>
        public virtual ServiceDescriptionImportWarnings
        GenerateCodeForProxyClass( ServiceDescription WSDLDocumentDescription,
                                   CodeCompileUnit myCodeCompileUnit )
        {
            ServiceDescriptionImporter myServiceDescriptionImporter = new ServiceDescriptionImporter();

            myServiceDescriptionImporter.ProtocolName = "Soap";
            myServiceDescriptionImporter.AddServiceDescription( WSDLDocumentDescription,
                                                                String.Empty, String.Empty );

            CodeNamespace myCodeNamespace = new CodeNamespace();
            myCodeCompileUnit.Namespaces.Add( myCodeNamespace );

            return myServiceDescriptionImporter.Import( myCodeNamespace, myCodeCompileUnit );
        }

        /// <summary>
        /// Compiles the proxy code into an in-memory assembly
        /// </summary>
        /// <param name="myCodeCompileUnit"></param>
        /// <returns></returns>
        public virtual Assembly CompileCodeForProxyClass( CodeCompileUnit myCodeCompileUnit )
        {
            CodeDomProvider myCodeDomProvider = CodeDomProvider.CreateProvider( "CSharp" );

            CompilerParameters myCompilerParameters = new CompilerParameters();
            myCompilerParameters.GenerateExecutable = false;
            myCompilerParameters.GenerateInMemory = true;
            myCompilerParameters.IncludeDebugInformation = false;
            myCompilerParameters.ReferencedAssemblies.Add( "System.dll" );
            myCompilerParameters.ReferencedAssemblies.Add( "System.Data.dll" );

            CompilerResults myCompilerResults =
                myCodeDomProvider.CompileAssemblyFromDom( myCompilerParameters,
                                                      myCodeCompileUnit );
            Assembly assembly = myCompilerResults.CompiledAssembly;

            if ( !EnableCaching || CacheDuration <= 0 )
                return assembly;

            Cache cache = HttpContext.Current.Cache;
            if ( cache[CacheKeyDependency] != null )
                cache.Remove( CacheKeyDependency );

            DateTime absoluteExpiration = Cache.NoAbsoluteExpiration;
            TimeSpan slidingExpiration = Cache.NoSlidingExpiration;

            if ( CacheExpirationPolicy == XmlWebServiceMethodInvokerCacheExpirationPolicy.Absolute )
                absoluteExpiration = DateTime.UtcNow.AddSeconds( ( CacheDuration == 0 ) ?
                                             ( ( double )0x7fffffff ) : ( ( double )CacheDuration ) );
            else
                slidingExpiration = TimeSpan.FromSeconds( ( double )CacheDuration );

            cache.Insert( CacheKeyDependency, assembly, null,
                         absoluteExpiration, slidingExpiration );
            return assembly;
        }



        /// <summary>
        /// Creates an instance of the proxy class
        /// </summary>
        /// <param name="WSDLDocumentDescription"></param>
        /// <param name="myAssembly"></param>
        /// <returns></returns>
        public virtual object CreateProxyClassInstance( Assembly assembly )
        {

            object instance = assembly.CreateInstance( TypeName );
            ( ( System.Web.Services.Protocols.SoapHttpClientProtocol )instance ).Credentials = CredentialCache.DefaultCredentials;
            return instance;
        }

        /// <summary>
        /// Invokes the specified method of the proxy instance
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="methodArgumentValues"></param>
        /// <param name="proxyClassInstance"></param>
        /// <param name="validateAndSortValues"></param>
        /// <returns></returns>
        public virtual Object InvokeProxyClassInstanceMethod( string methodName,
                                                                IOrderedDictionary methodArgumentValues,
                                                                    object proxyClassInstance,
                                                                    bool validateAndSortValues )
        {
            Type t = proxyClassInstance.GetType();
            MethodInfo[] mis = t.GetMethods();
            MethodInfo mi = null;

            foreach ( MethodInfo minfo in mis )
            {
                if ( minfo.Name == methodName )
                {
                    mi = minfo;
                    break;
                }
            }

            if ( mi == null )
                throw new Exception( "Method not found!" );

            if ( validateAndSortValues && methodArgumentValues != null && methodArgumentValues.Count > 0 )
                ValidateAndSortMethodArgumentNamesAndValues( mi, methodArgumentValues );

            if ( methodArgumentValues != null && methodArgumentValues.Count > 0 )
            {
                object[] values = new object[methodArgumentValues.Count];
                methodArgumentValues.Values.CopyTo( values, 0 );
                return mi.Invoke( proxyClassInstance, values );
            }
            else
                return mi.Invoke( proxyClassInstance, null );
        }

        /// <summary>
        /// Validates and sorts the arguments values before they are passed to the method
        /// being invoked
        /// </summary>
        /// <param name="mi"></param>
        /// <param name="values"></param>
        protected virtual void ValidateAndSortMethodArgumentNamesAndValues( MethodInfo mi, IOrderedDictionary values )
        {
            if ( values != null && values.Count > 0 )
            {
                ParameterInfo[] parameters = mi.GetParameters();
                OrderedDictionary list = new OrderedDictionary();

                foreach ( ParameterInfo parameter in parameters )
                {
                    bool found = false;
                    foreach ( DictionaryEntry entry in values )
                    {
                        if ( parameter.Name == entry.Key.ToString() )
                        {
                            found = true;
                            TypeConverter converter = TypeDescriptor.GetConverter( parameter.ParameterType );
                            if ( converter.CanConvertFrom( entry.Value.GetType() ) )
                                list.Add( entry.Key, converter.ConvertFrom( entry.Value ) );
                            else if ( converter.CanConvertFrom( Type.GetType( "System.String" ) ) )
                                list.Add( entry.Key, converter.ConvertFrom( entry.Value.ToString() ) );
                            else
                                throw new Exception( "Cannot convert value" );
                            break;
                        }
                    }

                    if ( !found )
                        throw new Exception( "Argument not found" );
                }

                values.Clear();
                foreach ( DictionaryEntry entry in list )
                {
                    values.Add( entry.Key, entry.Value );
                }
            }
        }

        /// <summary>
        /// Calls all the other methods of the XmlWebServiceMethodInvoker class in right order
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="methodArgumentNamesAndValues"></param>
        /// <param name="validateAndSortMethodArgumentNamesAndValues"></param>
        /// <returns></returns>
        public virtual object InvokeXmlWebServiceMethod( string methodName, IOrderedDictionary methodArgumentNamesAndValues,
                                                     bool validateAndSortMethodArgumentNamesAndValues )
        {
            Assembly assembly = null;

            Cache cache = HttpContext.Current.Cache;
            if ( EnableCaching && cache[CacheKeyDependency] != null )
                assembly = ( Assembly )cache[CacheKeyDependency];

            else
            {
                //XmlReader WSDLDocument = DownloadWSDLDocument();
                Stream WSDLDocument = DownloadWSDLDocument();
                ServiceDescription WSDLDocumentDescription = DeserializeWSDLDocument( WSDLDocument );
                CodeCompileUnit myCodeCompileUnit = new CodeCompileUnit();

                ServiceDescriptionImportWarnings warnings = GenerateCodeForProxyClass( WSDLDocumentDescription,
                                                                                      myCodeCompileUnit );

                if ( warnings == 0 )
                    assembly = CompileCodeForProxyClass( myCodeCompileUnit );
            }

            if ( assembly != null )
            {
                object proxyInstance = CreateProxyClassInstance( assembly );
                return InvokeProxyClassInstanceMethod( methodName, methodArgumentNamesAndValues, proxyInstance,
                                                        validateAndSortMethodArgumentNamesAndValues );
            }

            return null;
        }
    }

    // Summary:
    //     Describes the way data cached using ASP.NET caching mechanisms expires when
    //     a time-out is set.
    public enum XmlWebServiceMethodInvokerCacheExpirationPolicy
    {
        // Summary:
        //     Cached data expires when the amount of time specified by the CacheDuration
        //     property has passed since the data was first cached.
        Absolute = 0,
        //
        // Summary:
        //     Cached data expires only when the cache entry has not been used for the amount
        //     of time specified by the CacheDuration property.
        Sliding = 1,
    }
}
